package com.mindata.blockchain.socket.common.queue;

import com.mindata.blockchain.ApplicationContextProvider;
import com.mindata.blockchain.block.block.*;
import com.mindata.blockchain.block.instruction.Instruction;
import com.mindata.blockchain.common.CommonUtil;
import com.mindata.blockchain.common.Commonvalue;
import com.mindata.blockchain.common.Sha256;
import com.mindata.blockchain.common.timer.TimerManager;
import com.mindata.blockchain.core.manager.DbBlockManager;
import com.mindata.blockchain.socket.body.rpc.RpcDGLBlockBody;
import com.mindata.blockchain.socket.client.PacketSender;
import com.mindata.blockchain.socket.packet.BlockPacket;
import com.mindata.blockchain.socket.packet.PacketBuilder;
import com.mindata.blockchain.socket.packet.PacketType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * @author maltyshane
 */
@Component
public class GlobalBlockQueue {
    @Value("${publicKey}")
    String publicKey;
    @Value("${privateKey}")
    String privateKey;
    @Value("${appId}")
    String appId;
    @Value("${singeNode}")
    boolean singeNode;
    @Resource
    Commonvalue commonValue;
    @Resource
    PacketSender packetSender;
    private Logger logger = LoggerFactory.getLogger(GlobalBlockQueue.class);
    DbBlockManager dbBlockManager = ApplicationContextProvider.getBean(DbBlockManager.class);
    /**
     * 存储所有的hash的TX集合
     */
    public ConcurrentHashMap<String, Block> blockConcurrentHashMap = new ConcurrentHashMap<>();
    /**
     * 存储本节点已签名状态的hash的集合，即本节点已对外广播的交易消息
     */
    public ConcurrentHashMap<String, Boolean> blockStateConcurrentHashMap = new ConcurrentHashMap<>();
    public ConcurrentHashMap<Integer, ConcurrentLinkedQueue<Block>> numListConcurrentHashMap = new ConcurrentHashMap<>();

    public ConcurrentLinkedQueue<DGLBlock> dglblockQueue = new ConcurrentLinkedQueue<>();

    public void push(Block block) {
        String hash = block.getHash();
        Integer num = block.getBlockHeader().getNumber();
        if (!blockConcurrentHashMap.containsKey(block.getHash())) {
            blockConcurrentHashMap.put(hash, block);
            numListConcurrentHashMap.computeIfAbsent(num, k -> new ConcurrentLinkedQueue<>());
            numListConcurrentHashMap.get(num).offer(block);
        } else {
            logger.error("收到的区块已经处理过");
        }
//        logger.error("DGL共识收到的block数量为{}", numListConcurrentHashMap.get(num).size());
//        logger.warn("global共识 num={},shardnum={},myshardId={}", num, commonValue.getShardnum(), commonValue.getMygroupid());
        deal(num);
    }

    public void deal(Integer num) {
        if (blockConcurrentHashMap.isEmpty()) {
            logger.error("区块队列为空");
            return;
        }
        int concensusindex = (dbBlockManager.getLastBlockNumber() + 1) % commonValue.getGroupsize();
        if (!((dbBlockManager.getLastDGLBlockNumber() + 1) % commonValue.getShardnum() == commonValue.getMygroupid()
                && (singeNode||concensusindex == commonValue.getNodeIndex()))) {
//            logger.error("不是globalleader,不能出块");
            return;
        }
        // 当map中未处理的区块大于区块交易limit是生成区块
        if (numListConcurrentHashMap.get(num).size() >= commonValue.getShardnum()) {
            // 生成DGLBlock
            DGLBlock dglBlock = (DGLBlock) addDGLBlock(num);
            BlockPacket blockPacket = new PacketBuilder<>()
                    .setType(PacketType.DGL_GENERATE_BLOCK_REQUEST)
                    .setBody(new RpcDGLBlockBody(dglBlock)).build();
            logger.error("global leader开始广播dglVotePreMsg消息");
            packetSender.sendToLeaderSet(blockPacket);
        }
    }

    public BaseBlock addDGLBlock(Integer num) {
        List<Block> blockList = new ArrayList<>();
        ConcurrentLinkedQueue<Block> numblockQueue = numListConcurrentHashMap.get(num);
        Block block;
        List<String> hashList = new ArrayList<>();
        int size = commonValue.getShardnum();
        for (int i = 0; i < size; i++) {
            do {
                block = numblockQueue.poll();
            } while (block == null);
            blockList.add(block);
            hashList.add(block.getHash());
            blockStateConcurrentHashMap.put(block.getHash(), true);
        }
        DGLBlockHeader dglBlockHeader = new DGLBlockHeader();
        dglBlockHeader.setBlockhashList(hashList);
        //计算所有指令的hashRoot
        dglBlockHeader.setPublicKey(publicKey);
        dglBlockHeader.setTimeStamp(CommonUtil.getNow());
        dglBlockHeader.setVersion(blockList.get(0).getBlockHeader().getVersion());
        dglBlockHeader.setNumber(blockList.get(0).getBlockHeader().getNumber());
        dglBlockHeader.setHashPreviousBlock(dbBlockManager.getLastDGLBlockHash());
        DGLBlock dglBlock = new DGLBlock();
        dglBlock.setBlockBody(new BlockBody());
        dglBlock.setDglBlockHeader(dglBlockHeader);
        dglBlock.setHash(Sha256.sha256(dglBlockHeader.toString() + dglBlock.getBlockBody().toString()));
        dglBlock.setBlockList(blockList);
        dglblockQueue.offer(dglBlock);
        return dglBlock;
    }


    /**
     * 清理已经提交的instruction的hash
     */
    public void clearOldInstructionHash(Block block) {
        List<Instruction> instructions = block.getBlockBody().getInstructions();
        TimerManager.schedule(() -> {
            for (Instruction temp : instructions) {
                if (blockConcurrentHashMap.containsKey(temp.getHash())) {
                    blockConcurrentHashMap.remove(temp.getHash());
                    blockStateConcurrentHashMap.remove(temp.getHash());
                    // TODO
                    /**
                     * 移除提交到区块链中但还存在于交易池中的交易
                     */
                }
            }
            return null;
        }, 2000);
    }

    public boolean isExist(Object obj) {
        return (blockConcurrentHashMap.containsKey(obj));
    }
}



